package xyz.lwm.lazycat.jdbc.exec;

import org.jetbrains.annotations.NotNull;
import xyz.lwm.lazycat.utility.Assert;
import xyz.lwm.lazycat.utility.convert.Convert;
import xyz.lwm.lazycat.utility.convert.Types;
import xyz.lwm.lazycat.utility.reflect.ReflectUtil;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * common row mapper implementations
 *
 * @author lwm
 */
public class RowMappers {

    /**
     * get row mapper by class type
     *
     * @param type class
     * @param <T>  generic type
     * @return row mapper
     */
    @SuppressWarnings("unchecked")
    public static <T> RowMapper<T> getRowMapper(Class<T> type) {

        if (Types.isBaseType(type)) {
            return new BaseRowMapper<>(type);

        } else if (Map.class.isAssignableFrom(type)) {
            return (RowMapper<T>) new MapRowMapper();

        } else {
            return new BeanRowMapper<>(type);
        }

    }

    public static class BaseRowMapper<T> extends RowMapperSupport<T> {

        private final Class<T> type;

        public BaseRowMapper(Class<T> type) {
            Assert.isTrue(Types.isBaseType(type), "It's not base type");
            this.type = type;
        }

        @NotNull
        @Override
        protected RowValue<T> createRow(int columnCount) {
            return new RowValue<T>(null) {
                private final AtomicReference<T> ref = new AtomicReference<>();
                @Override
                protected void setColumnValue(String column, Object columnValue) {
                    if (ref.get() == null) {
                        T t = Convert.to(columnValue, type);
                        ref.set(t);
                    }
                }
                @Override
                protected T get() {
                    return ref.get();
                }
            };
        }
    }


    public static class BeanRowMapper<T> extends RowMapperSupport<T> {

        private final Class<T> type;

        public BeanRowMapper(Class<T> type) {
            Assert.isTrue(!Map.class.isAssignableFrom(type) && !Collection.class.isAssignableFrom(type),
                    "impl '{}' cannot be map or collection.", type.getName());
            this.type = type;
        }

        @NotNull
        @Override
        protected RowValue<T> createRow(int columnCount) {
            return new RowValue<T>(ReflectUtil.newInstance(type)) {

                private final Map<String, Field> fieldMap = ReflectUtil.findDeclaredFields(type);

                @Override
                protected void setColumnValue(String column, Object columnValue) {
                    Field field = fieldMap.get(column);
                    if (field != null) {
                        Object value = Convert.to(columnValue, field.getType());
                        ReflectUtil.setValue(get(), field, value);
                    }
                }
            };
        }
    }

    public static class MapRowMapper extends RowMapperSupport<Map<String, Object>> {

        @NotNull
        @Override
        protected RowValue<Map<String, Object>> createRow(int columnCount) {
            return new RowValue<Map<String, Object>>(new HashMap<>()) {
                @Override
                protected void setColumnValue(String column, Object columnValue) {
                    get().putIfAbsent(column, columnValue);
                }
            };
        }

    }

}